﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Services.Protocols;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Metadata;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using System.IO;


namespace OrbitOne.CRM.Plugins.MatchnamePluginInstaller
{
    class MatchNamePluginInstall
    {
        private static CrmService m_service;
        private static MetadataService m_metadataService;
        private static CrmServiceWrapper m_serviceWrapper;
        private static List<string> m_suffixes = new List<string>
                                              {
                                                  "nv",
                                                  "sa",
                                                  "vzw",
                                                  "bvba",
                                                  "cvba",
                                                  "sarl",
                                                  "sprl",
                                                  "scrl",
                                                  "nvsa",
                                                  "bv",
                                                  "llc",
                                                  "gmbh",
                                                  "ltd",
                                                  "ag",
                                                  "kt",
                                                  "og",
                                                  "vof",
                                                  "cvoa",
                                                  "commva",
                                                  "commv",
                                                  "ebvba",
                                                  "ez",
                                                  "belgium"
                                              };

        private static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Creating services");
                m_serviceWrapper = new CrmServiceWrapper();
                m_metadataService = m_serviceWrapper.MetaDataService;
                m_service = m_serviceWrapper.Service;

                Console.WriteLine("Creating new attributes for plugin on Entities account, lead and contact");
                createPluginAttributes();

                Console.WriteLine("Processing the records");
                processRecordsForPlugin();

                Console.WriteLine("Start plugin registration");
                registerPlugin();

                Console.WriteLine("Adding the duplicate detection rules");
                addDuplicateDetectionRules();
            }
            catch (SoapException ex)
            {
                Console.WriteLine(ex.Detail.InnerXml);
                Console.WriteLine(ex);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private static void addDuplicateDetectionRules()
        {
            addDuplicateDetectionRule(EntityName.contact.ToString(), "new_matchname");
            addDuplicateDetectionRule(EntityName.account.ToString(), "new_matchname");
            addDuplicateDetectionRule(EntityName.lead.ToString(), "new_matchcontact");
        }

        private static void addDuplicateDetectionRule(string entityName, string fieldName)
        {
            var rule = new DynamicEntity("duplicaterule");
            rule.Properties.AddRange(new Property[]
                                         {
                                             new StringProperty("baseentityname", entityName),
                                             new StringProperty("matchingentityname", entityName),
                                             new StringProperty("description",
                                                                "Detects " + entityName +
                                                                " records that have the same value in the " +
                                                                fieldName.Replace("new_", "") +
                                                                " attribute."),
                                             new StringProperty("name",
                                                                entityName.Substring(0, 1).ToUpper() +
                                                                entityName.Substring(1) + "s with the same " +
                                                                fieldName.Replace("new_", "")),
                                             new CrmBooleanProperty("iscasesensitive", new CrmBoolean(false))
                                         });

            var ruleId = m_service.Create(rule);

            var ruleCondition = new DynamicEntity("duplicaterulecondition");
            ruleCondition.Properties.AddRange(new Property[]
                                                  {
                                                      new StringProperty("baseattributename", fieldName),
                                                      new StringProperty("matchingattributename", fieldName),
                                                      new PicklistProperty("operatorcode", new Picklist(0)),
                                                      new LookupProperty("regardingobjectid", new Lookup("duplicaterule", ruleId))
                                                  });
            m_service.Create(ruleCondition);
            m_service.Execute(new PublishDuplicateRuleRequest { DuplicateRuleId = ruleId });
        }

        private static void createPluginAttributes()
        {
            var matchName = new StringAttributeMetadata
            {
                SchemaName = "new_matchname",
                DisplayName = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Match Name", 1033),
                RequiredLevel = new CrmAttributeRequiredLevel() { Value = AttributeRequiredLevel.None },
                Description = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Stores the match name which helps duplicate detection", 1033),
                AttributeType = new CrmAttributeType(AttributeType.String),
                MaxLength = new CrmNumber(255)
            };

            var createAttributeRequest = new CreateAttributeRequest
            {
                Attribute = matchName,
            };

            createAttributeRequest.EntityName = "contact";
            m_metadataService.Execute(createAttributeRequest);

            createAttributeRequest.EntityName = "account";
            m_metadataService.Execute(createAttributeRequest);

            createAttributeRequest.EntityName = "lead";
            matchName.SchemaName = "new_matchcontact";
            matchName.DisplayName = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Match Name Contact", 1033);
            matchName.Description = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Stores the match name of the contact which helps duplicate detection", 1033);
            m_metadataService.Execute(createAttributeRequest);
            matchName.SchemaName = "new_matchcompany";
            matchName.DisplayName = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Match Name Company", 1033);
            matchName.Description = Microsoft.Crm.Sdk.Utility.CrmServiceUtility.CreateSingleLabel("Stores the match name of the company which helps duplicate detection", 1033);
            m_metadataService.Execute(createAttributeRequest);

            m_service.Execute(new PublishAllXmlRequest());
        }
        private static void processRecordsForPlugin()
        {
            Console.WriteLine("Processing the contacts");
            processRecordsForPlugin("contact",
                                    new[] { "contactid", "firstname", "lastname" },
                                    ent =>
                                    {
                                        var name = ((ent.Properties.Contains("firstname") ? ent.Properties["firstname"] ?? "" : "").ToString()
                                                    + " "
                                                    + (ent.Properties.Contains("lastname") ? ent.Properties["lastname"] ?? "" : "").ToString()).Trim();
                                        ent.Properties.Add(new StringProperty("new_matchname", normalize(name)));
                                    });
            Console.WriteLine("Processing the accounts");
            processRecordsForPlugin("account",
                                    new[] { "accountid", "name" },
                                    ent =>
                                    {
                                        var name = (ent.Properties.Contains("name") ? ent.Properties["name"] ?? "" : "").ToString();
                                        ent.Properties.Add(new StringProperty("new_matchname", normalize(name)));
                                    });
            Console.WriteLine("Processing the leads");
            processRecordsForPlugin("lead",
                                    new[] { "leadid", "firstname", "lastname", "companyname" },
                                    ent =>
                                    {
                                        var name = (ent.Properties.Contains("companyname") ? ent.Properties["companyname"] ?? "" : "").ToString();
                                        ent.Properties.Add(new StringProperty("new_matchcompany", normalize(name)));

                                        name = ((ent.Properties.Contains("firstname") ? ent.Properties["firstname"] ?? "" : "").ToString()
                                                    + " "
                                                    + (ent.Properties.Contains("lastname") ? ent.Properties["lastname"] ?? "" : "").ToString()).Trim();
                                        ent.Properties.Add(new StringProperty("new_matchcontact", normalize(name)));
                                    });

        }
        private static void registerPlugin()
        {
            var config = CrmConfiguration.Load();
            m_pluginregistrationxml = string.Format(m_pluginregistrationxml, config.Url, config.Instance, config.Domain, config.Username);
            m_connections = string.Format(m_connections, config.Url, config.Domain, config.Username);

            var sw = new StreamWriter("MatchNameConnection.config");
            sw.Write(m_connections);
            sw.Flush();
            sw.Close();
            sw = new StreamWriter("MatchNameReg.xml");
            sw.Write(m_pluginregistrationxml);
            sw.Flush();
            sw.Close();
            var process = new ProcessStartInfo("PluginRegistration.exe",
                                               "/c:MatchNameConnection.config /f:MatchNameReg.xml /cl:PluginDeployment /op:import /n:false /org:"
                                                 + config.Instance);

            Process.Start(process);
        }

        private static void processRecordsForPlugin(string entityName, string[] columnset, Action<DynamicEntity> setMatchnameProperties)
        {
            var query = new QueryExpression(entityName)
            {
                ColumnSet = new ColumnSet(columnset),
                PageInfo = new PagingInfo { Count = 1000, PageNumber = 1 }
            };

            bool moreRecords = true;
            var entities = new List<BusinessEntity>();
            while (moreRecords)
            {
                var temp = (RetrieveMultipleResponse)m_service.Execute(new RetrieveMultipleRequest
                {
                    Query = query,
                    ReturnDynamicEntities = true
                });
                entities.AddRange(temp.BusinessEntityCollection.BusinessEntities);

                query.PageInfo.PageNumber++;
                moreRecords = temp.BusinessEntityCollection.MoreRecords;
                query.PageInfo.PagingCookie = temp.BusinessEntityCollection.PagingCookie;
            }

            entities.Cast<DynamicEntity>().ToList().ForEach(setMatchnameProperties);
            entities.ForEach(m_service.Update);
        }
        private static string normalize(string name)
        {
            name = (name ?? "").ToLower().Trim();
            name = Regex.Replace(name, "[^0-9A-Za-zà-ýÀ-Ý ()]", "");
            name = removeStuffBetweenParenthesis(name);
            name = removeSuffix(name);
            name = name.Replace(" ", "");
            name = removeSpecialCharacters(name);
            return name;
        }
        private static string removeStuffBetweenParenthesis(string toClean)
        {
            while (toClean.Contains("(") && toClean.Contains(")"))
            {
                toClean = (toClean.IndexOf("(") != 0
                            ? toClean.Substring(0, toClean.IndexOf("(") - 1)
                            : "")
                       + toClean.Substring(toClean.IndexOf(")", toClean.IndexOf("(")) + 1);
            }
            return toClean;
        }
        private static string removeSuffix(string toClean)
        {
            while (m_suffixes.Any(suffix => toClean.EndsWith(" " + suffix) || toClean.StartsWith(suffix + " ")))
            {
                foreach (var suffix in m_suffixes)
                {
                    if (toClean.EndsWith(" " + suffix))
                    {
                        toClean = toClean.Substring(0, toClean.Length - suffix.Length - 1);
                    }
                    if (toClean.StartsWith(suffix + " "))
                    {
                        toClean = toClean.Substring(suffix.Length + 1);
                    }
                }
            }

            return toClean;
        }

        private static string removeSpecialCharacters(IEnumerable<char> toProcess)
        {
            var sb = new StringBuilder();
            foreach (var c in toProcess)
            {
                sb.Append(getSearchNameCharacter(c));
            }
            return Regex.Replace(sb.ToString(), "[^0-9A-Za-z]", "");
        }
        private static string getSearchNameCharacter(char character)
        {
            int code = character;
            string retval;
            switch (code)
            {
                case 138:
                    retval = "S";
                    break;
                case 140:
                    retval = "OE";
                    break;
                case 142:
                    retval = "Z";
                    break;
                case 154:
                    retval = "s";
                    break;
                case 156:
                    retval = "oe";
                    break;
                case 158:
                    retval = "z";
                    break;
                case 159:
                    retval = "Y";
                    break;
                case 197:
                case 196:
                case 195:
                case 194:
                case 193:
                case 192:
                    retval = "A";
                    break;
                case 198:
                    retval = "AE";
                    break;
                case 199:
                    retval = "C";
                    break;
                case 203:
                case 202:
                case 201:
                case 200:
                    retval = "E";
                    break;
                case 207:
                case 206:
                case 205:
                case 204:
                    retval = "I";
                    break;
                case 208:
                    retval = "D";
                    break;
                case 209:
                    retval = "N";
                    break;
                case 214:
                case 213:
                case 212:
                case 211:
                case 210:
                    retval = "O";
                    break;
                case 215:
                    retval = "x";
                    break;
                case 216:
                    retval = "O";
                    break;
                case 220:
                case 219:
                case 218:
                case 217:
                    retval = "U";
                    break;
                case 221:
                    retval = "Y";
                    break;
                case 223:
                    retval = "ss";
                    break;
                case 229:
                case 228:
                case 227:
                case 226:
                case 225:
                case 224:
                    retval = "a";
                    break;
                case 230:
                    retval = "oe";
                    break;
                case 231:
                    retval = "c";
                    break;
                case 235:
                case 234:
                case 233:
                case 232:
                    retval = "e";
                    break;
                case 239:
                case 238:
                case 237:
                case 236:
                    retval = "i";
                    break;
                case 240:
                    retval = "o";
                    break;
                case 241:
                    retval = "n";
                    break;
                case 246:
                case 245:
                case 244:
                case 243:
                case 242:
                    retval = "o";
                    break;
                case 248:
                    retval = "o";
                    break;
                case 252:
                case 251:
                case 250:
                case 249:
                    retval = "u";
                    break;
                case 253:
                    retval = "y";
                    break;
                case 255:
                    retval = "y";
                    break;
                default:
                    retval = character.ToString();
                    break;
            }

            return retval;
        }

        private static string m_connections =
            @"<?xml version=""1.0""?>
<ArrayOfCrmConnection xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
  <CrmConnection>
    <Label>PluginDeployment</Label>
    <DiscoveryServerName>{0}</DiscoveryServerName>
    <DiscoveryServerPort xsi:nil=""true"" />
    <UserDomain>{1}</UserDomain>
    <UserName>{2}</UserName>
  </CrmConnection>
</ArrayOfCrmConnection>";
        private static string m_pluginregistrationxml =
            @"<Register LogFile=""Plug-in Registration Log.txt"" Server=""{0}"" Org=""{1}"" Domain=""{2}"" UserName=""{3}"">
  <Solution SourceType=""0"" Assembly=""OrbitOne.CRM.Plugins.dll"" Id=""039c7ae4-fce5-403c-9252-eb3bde3d2802"">
    <PluginTypes>
      <Plugin TypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" FriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" Id=""424ecd5d-e6fe-4ce6-b921-43a9b89f3bfa"">
        <Steps>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Update of contact in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Update"" Mode=""0"" PrimaryEntityName=""contact"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""e5be92fe-7c98-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Create of contact in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Create"" Mode=""0"" PrimaryEntityName=""contact"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""f7e63f93-7a98-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Update of account in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Update"" Mode=""0"" PrimaryEntityName=""account"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""a4f73e86-6699-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Create of account in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Create"" Mode=""0"" PrimaryEntityName=""account"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""4456048d-6699-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Update of lead in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Update"" Mode=""0"" PrimaryEntityName=""lead"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""2a9b6c96-6699-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
          <Step PluginTypeName=""OrbitOne.CRM.Plugins.MatchNamePlugin"" PluginTypeFriendlyName=""99166952-125b-4fcc-8f7a-4a72f2c28c41"" CustomConfiguration="""" SecureConfiguration="""" Description=""Create of lead in Parent Pipeline"" FilteringAttributes="""" ImpersonatingUserId="""" InvocationSource=""0"" MessageName=""Create"" Mode=""0"" PrimaryEntityName=""lead"" SecondaryEntityName=""none"" Stage=""10"" SupportedDeployment=""0"" Rank=""1"" Id=""e49cba9c-6699-de11-971d-005056bd3ea9"">
            <Images />
          </Step>
        </Steps>
      </Plugin>
    </PluginTypes>
  </Solution>
</Register>";
    }
}
